package com.cyj.dream.gateway.filter;

import cn.hutool.core.util.StrUtil;
import com.cyj.dream.captcha.callback.CacheCallback;
import com.cyj.dream.captcha.core.CaptchaResult;
import com.cyj.dream.captcha.util.CaptchaUtil;
import com.cyj.dream.core.constant.CacheConstants;
import com.cyj.dream.core.constant.SecurityConstants;
import com.cyj.dream.core.constant.pagemodel.ResponseUtil;
import com.cyj.dream.core.exception.ValidateCodeException;
import com.cyj.dream.core.util.weborspring.WebUtils;
import com.cyj.dream.gateway.config.GatewayConfigProperties;
import com.cyj.dream.gateway.constant.ImageCodeConstant;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Description: 验证码处理过滤器
 * @BelongsProject: DreamChardonnay
 * @BelongsPackage: com.cyj.dream.gateway.filter
 * @Author: ChenYongJia
 * @CreateTime: 2021-09-27 13:05
 * @Email: chen87647213@163.com
 * @Version: 1.0
 */
@Slf4j
@RequiredArgsConstructor
public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory {

    private final GatewayConfigProperties configProperties;

    private final ObjectMapper objectMapper;

    private final RedisTemplate redisTemplate;

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();

            // 不是登录请求，直接向下执行
            if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL)) {
                return chain.filter(exchange);
            }

            // 刷新token，直接向下执行
            String grantType = request.getQueryParams().getFirst("grant_type");
            if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)) {
                return chain.filter(exchange);
            }

            // 终端设置不校验， 直接向下执行
            try {
                String[] clientInfos = WebUtils.getClientId(request);
                if (configProperties.getIgnoreClients().contains(clientInfos[0])) {
                    return chain.filter(exchange);
                }

                // 校验验证码
                checkCode(request);
            } catch (Exception e) {
                ServerHttpResponse response = exchange.getResponse();
                response.setStatusCode(HttpStatus.PRECONDITION_REQUIRED);
                response.getHeaders().setContentType(MediaType.APPLICATION_JSON);

                final String errMsg = e.getMessage();
                return response.writeWith(Mono.create(monoSink -> {
                    try {
                        byte[] bytes = objectMapper.writeValueAsBytes(ResponseUtil.error(errMsg));
                        DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);

                        monoSink.success(dataBuffer);
                    } catch (JsonProcessingException jsonProcessingException) {
                        log.error("对象输出异常", jsonProcessingException);
                        monoSink.error(jsonProcessingException);
                    }
                }));
            }

            return chain.filter(exchange);
        };
    }

    /**
     * 检查code
     *
     * @param request
     */
    @SneakyThrows
    private void checkCode(ServerHttpRequest request) {
        String type = request.getQueryParams().getFirst("type");
        if (ImageCodeConstant.IMAGE_TYPE_DRAG.equals(type)) {
            this.checkDragCode(request);
        } else {
            this.checkStrCode(request);
        }

    }

    /**
     * 校验拖拽验证码
     *
     * @param request
     */
    private void checkDragCode(ServerHttpRequest request) {
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        String randomStr = queryParams.getFirst("randomStr");
        CaptchaResult verify = CaptchaUtil.verify(queryParams, randomStr, this.getCacheCallback());
        if (!verify.isSuccess()) {
            throw new ValidateCodeException(verify.getMessage());
        }
    }

    /**
     * 校验字符串输入的验证码
     *
     * @param request
     */
    private void checkStrCode(ServerHttpRequest request) {
        String code = request.getQueryParams().getFirst("code");

        if (StrUtil.isBlank(code)) {
            throw new ValidateCodeException("验证码不能为空");
        }

        String randomStr = request.getQueryParams().getFirst("randomStr");
        if (StrUtil.isBlank(randomStr)) {
            randomStr = request.getQueryParams().getFirst("mobile");
        }

        String key = CacheConstants.DEFAULT_CODE_KEY + randomStr;
        if (!redisTemplate.hasKey(key)) {
            throw new ValidateCodeException("验证码不合法");
        }

        Object codeObj = redisTemplate.opsForValue().get(key);

        if (codeObj == null) {
            throw new ValidateCodeException("验证码不合法");
        }

        String saveCode = codeObj.toString();
        if (StrUtil.isBlank(saveCode)) {
            redisTemplate.delete(key);
            throw new ValidateCodeException("验证码不合法");
        }

        if (!StrUtil.equals(saveCode, code)) {
            redisTemplate.delete(key);
            throw new ValidateCodeException("验证码不合法");
        }

        redisTemplate.delete(key);
    }


    private CacheCallback getCacheCallback() {
        return new CacheCallback() {
            @Override
            public void removeCache(String cacheKey) {
                redisTemplate.delete(cacheKey);
            }

            @Override
            public void putCache(String cacheKey, Object value, long exp, TimeUnit timeUnit) {
                redisTemplate.opsForValue().set(cacheKey, value,
                        SecurityConstants.CODE_TIME, TimeUnit.SECONDS);
            }

            @Override
            public Map<String, Object> getCache(String cacheKey) {
                Object o = redisTemplate.opsForValue().get(cacheKey);
                if (o instanceof Map) {
                    return (Map) o;
                }
                return null;
            }
        };
    }
}

